package com.scientific.research.gobang.service.codegame.impl;

import com.scientific.research.gobang.service.RunCodeService;
import com.scientific.research.gobang.utils.FileUtils;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Service
@Scope("prototype")
//此类主要负责执行一次代码，包括代码编译，代码单次执行
public class RunCodeServiceImpl implements RunCodeService {

    //用户操作系统类型
    private static String osType;
    //用户运行代码的目录
    static String basePath;

    //准备代码运行环境
    public static void runCodeServiceInit() {
        osType = FileUtils.getOs();
        if ("windows".equals(osType)) {
            basePath = FileUtils.getBasePath() + "\\RunCodeSpace";
        } else {
            basePath = FileUtils.getBasePath() + "/RunCodeSpace";
        }
        File file = new File(basePath);
        if (!file.exists()) {
            if (!file.mkdir()) {
                System.out.println("创建 " + basePath + "目录失败！");
            }
        }
    }

    //用户名
    private String userName = "";
    //用户提交代码的语言
    private String language = "";

    //该变量用于判断该用户单次执行所执棋子颜色
    private int isMeBlack = 0;//1为我是黑棋，-1为别人是黑棋

    //拼接路径
    String appendPath(String path, String append) {
        if ("windows".equals(osType)) {
            path += "\\" + append;
            return path;
        } else {
            path += "/" + append;
            return path;
        }
    }

    /**
     * 存储java代码的编译代码的文件，bat或sh
     */
    private void saveJavaCompileFile() {
        String runCode;
        String path;
        if ("windows".equals(osType)) {
            runCode = "C:\r\n" + "cd " + basePath + "\\" + userName + "\r\n" +
                    "javac java.java";
            path = basePath + "\\" + userName + ".bat";
        } else {
            runCode = "cd " + basePath + "/" + userName + "\n" + "javac java.java";
            path = basePath + "/" + userName + ".sh";
        }
        FileUtils.writer(path, runCode, false);
    }

    //存储c++代码的编译代码的文件，bat或sh
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    private void saveCAddAddCompileFile() {
        String runCode;//-Wl,--heap=134217728 -Wl,--stack=262144参数为堆栈内存限制，-std=c++11采用C11标准
        String path;
        if ("windows".equals(osType)) {
            runCode = "C:\r\n" + "cd " + basePath + "\\" + userName + "\r\n" +
                    "g++ -Wl,--heap=134217728 -Wl,--stack=262144 -std=c++11 c++.cpp";
            path = basePath + "\\" + userName + ".bat";
        } else {
            runCode = "cd " + basePath + "/" + userName + "\n" + "g++ -std=c++11 c++.cpp -o a";
            path = basePath + "/" + userName + ".sh";
        }
        FileUtils.writer(path, runCode, false);
    }

    //添加java jvm相关参数
    private String addJavaRunJvm() {
        return "-Xmx128m -Xss256k ";//命令后面加一个空格
    }

    //获取java运行代码命令
    private String getJavaRunCommand() {
        String cmd = "java ";
        cmd += addJavaRunJvm() + "-classpath ";
        String path = appendPath(basePath, userName);
        cmd += path + " java";
        return cmd;
    }

    //获取c++运行代码命令
    @SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
    private String getCAddAddRunCommand() {
        String cmd = appendPath(basePath, userName);
        if ("windows".equals(osType)) {
            cmd = appendPath(cmd, "a.exe");
        } else {
            cmd = appendPath(cmd, "a");
        }
        return cmd;
    }


    //读取process错误流信息
    private String readOutputMessage(InputStream inputStream) //读取输入和错误流,1为非空，0为空
    {
        StringBuilder result = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("GBK")))) {
            String line;
            while ((line = reader.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            System.out.println("用户 " + userName + " 读取控制台输出错误！");
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result.toString();
    }

    private void writeInputMessage(OutputStream outputStream, String message) {
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
            writer.write(message);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //初始化当前变量，准备运行环境
    void thisInit(String userName, String language) {
        userName = userName.replace(".", "-");
        this.userName = userName;
        this.language = language;
        String path = appendPath(basePath, userName);
        if (!new File(path).mkdir()) {
            System.out.println("创建目录 " + path + "失败！");
        }
        //写入input文件
        //input文件用于存储棋盘信息，格式为111#222#333#444#
        FileUtils.writer(appendPath(path, "input.txt"), "", false);
        //写入对应语言的可执行文件
        if ("java".equals(language)) {
            saveJavaCompileFile();
        } else if ("c++".equals(language) || "c".equals(language)) {
            saveCAddAddCompileFile();
        }
    }

    //编译代码，准备运行环境
    @Override
    public String setAndCompile(String userName, String language) {
        String result = "";
        String path = appendPath(basePath, this.userName);

        if ("windows".equals(osType)) {
            path += ".bat";
        } else {
            path = "sh " + path + ".sh";
        }
        try {
            Process process = Runtime.getRuntime().exec(path);
            try {
                process.waitFor();
                process.destroy();
            } catch (InterruptedException e) {
                System.out.println("执行用户" + userName + "批处理命令时出错！");
                e.printStackTrace();
            }
            result = readOutputMessage(process.getErrorStream());
        } catch (IOException e) {
            System.out.println("执行用户" + userName + "批处理命令时出错！");
            e.printStackTrace();
        }
        return result;
    }

    //执行一次批处理命令并进行超时判断,
    private Map runProcess(String cmd, int second) {
        final Process[] process = new Process[1];
        CompletableFuture<Map> future = CompletableFuture.supplyAsync(() -> {
            String result = "";
            String value = "";
            Map<String, String> map = new HashMap<>();
            try {
                String outputPaht = appendPath(basePath, userName);
                outputPaht = appendPath(outputPaht, "input.txt");
                process[0] = Runtime.getRuntime().exec(cmd);
                writeInputMessage(process[0].getOutputStream(), FileUtils.read(outputPaht) + "\n");
                result = readOutputMessage(process[0].getErrorStream());
                value = readOutputMessage(process[0].getInputStream());
                try {
                    process[0].waitFor();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } catch (IOException e) {
                System.out.println("用户 " + userName + " 执行运行代码批处理命令失败！");
                e.printStackTrace();
            }
            map.put("result", result);
            map.put("value", value);
            return map;
        });
        Map<String, String> result = new HashMap<>();
        try {
            long startTime = System.currentTimeMillis();
            Map soon = future.get(second, TimeUnit.SECONDS);
            long endTime = System.currentTimeMillis();
            result.put("time", String.valueOf(endTime - startTime));
            result.put("result", (String) soon.get("result"));
            result.put("value", (String) soon.get("value"));
        } catch (Exception e) {
            result.put("error", "运行时间超过 " + second + " 秒，请检查算法的时间复杂度是否过高");
            process[0].destroy();
        }
        return result;
    }

    //执行一次代码
    @Override
    public Map doOnceRun()//运行一次代码
    {
        Map<String, String> lastResult = new HashMap<>();
        String cmd = "";
        String value;
        String path;
        if ("java".equals(language)) {
            cmd = getJavaRunCommand();
        } else if ("c++".equals(language) || "c".equals(language)) {
            cmd = getCAddAddRunCommand();
        }

        Map runResult = runProcess(cmd, 2);
        if (runResult.get("error") == null) {
            value = (String) runResult.get("result");
            if (value.length() == 0) {
                value = (String) runResult.get("value");
                path = appendPath(basePath, userName);
                path = appendPath(path, "input.txt");
                FileUtils.writer(path, value + "#", true);
                lastResult.put("value", value);
                lastResult.put("time", (String) runResult.get("time"));
            } else {
                lastResult.put("error", value);
            }
        } else {
            lastResult.put("timeOut", (String) runResult.get("error"));
        }
        return lastResult;
    }

    @Override
    public void setInput(String coordinates)//设置输入文件
    {
        String path = appendPath(basePath, userName);
        path = appendPath(path, "input.txt");
        FileUtils.writer(path, coordinates + "#", true);
    }

    @Override
    public void clearInputFile() {
        String path = appendPath(basePath, userName);
        path = appendPath(path, "input.txt");
        FileUtils.deleteFile(new File(path));
    }

    @Override
    public void destroy()//销毁该用户的文件夹
    {
        String path = appendPath(basePath, userName);
        FileUtils.deleteDirectory(new File(path));
        if ("windows".equals(osType)) {
            FileUtils.deleteFile(new File(path + ".bat"));
        } else {
            FileUtils.deleteFile(new File(path + ".sh"));
        }
    }

    @Override
    public int check()//检查是否成局,1为成局，0为不成，-1代表重复落子直接判负
    {
        String path = appendPath(basePath, userName);
        path = appendPath(path, "input.txt");
        String in = FileUtils.read(path);//将文件内容放入字符串in
        String ss;//每一个被#切割的字符串数字，代表着这一步棋的落子点
        int[][] input = new int[15][15];//棋盘
        int tag = 0;//tag代表这一步是黑棋还是白起
        while (!"\n".equals(in) && !"".equals(in))//将字符串in的内容写入input数组，坐标范围为0~14。
        {
            //将in以#切割，取碰到的第一个字符串，就是这一步的落子点
            ss = in.substring(0, in.indexOf("#"));
            if (!"".equals(ss)) {
                //x是棋盘行,y是棋盘列
                int i = Integer.valueOf(ss), x = i / 100, y = i % 100;
                //如果这一步已经被落子，说明“重复落子”，直接判负
                if (input[x][y] != 0) {
                    return -1;
                }
                if (tag == 0) {
                    input[x][y] = 1;//1为我是黑棋，-1为白棋
                    tag = 1;
                } else {
                    input[x][y] = -1;
                    tag = 0;
                }

                //把in字符串挪动到下一步
                in = in.substring(in.indexOf("#") + 1);
            }
        }
        //全局检查棋盘，看是否成局
        for (int x = 0; x < 15; x++) {
            for (int y = 0; y < 15; y++) {
                if (checkOneCheer(x, y, input) == 1) {
                    return 1;
                }
            }
        }
        return 0;
    }

    //check()的子方法，检查是否成局,1为成局，0为不成
    @Override
    public int checkOneCheer(int x, int y, int[][] map) {
        int i, j, cheerNum;
        //检查右边有没有5个
        cheerNum = 0;
        for (i = x; i < 15 && map[i][y] == isMeBlack; i++) {
            cheerNum += 1;
        }
        if (cheerNum >= 5) {
            return 1;
        }
        //检查下面有没有5个
        cheerNum = 0;
        for (i = y; i >= 0 && map[x][i] == isMeBlack; i--) {
            cheerNum += 1;
        }
        if (cheerNum >= 5) {
            return 1;
        }
        //检查斜左下有没有5个
        cheerNum = 0;
        for (i = x, j = y; i < 15 && j >= 0 && map[i][j] == isMeBlack; i++, j--) {
            cheerNum += 1;
        }
        if (cheerNum >= 5) {
            return 1;
        }
        //检查斜右下有没有5个
        cheerNum = 0;
        for (i = x, j = y; i < 15 && j < 15 && map[i][j] == isMeBlack; i++, j++) {
            cheerNum += 1;
        }
        if (cheerNum >= 5) {
            return 1;
        }

        return 0;
    }

    //设置我是黑棋
    @Override
    public void setIsMeBlack(int i) {
        this.isMeBlack = i;
    }

    String getUserName() {
        return userName;
    }

    String getLanguage() {
        return language;
    }

    void setUserName(String userName) {
        this.userName = userName;
    }

    void setLanguage(String language) {
        this.language = language;
    }
}
